setup

rm(list = ls())
library(tidyverse)
library(ggplot2)
library(car)
library(scam)

download data

if (file.exists("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")){
  data.working <- read.csv("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")
} else {
  download.file("https://github.com/sstangl/openpowerlifting-static/raw/gh-pages/openpowerlifting-latest.zip",
              "openpowerlifting-latest.zip")
  unzip("openpowerlifting-latest.zip")
  
  data.working <- read.csv("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")
}

summarize data

summary(data.working)
     Name               Sex               Event            Equipment              Age           AgeClass         BirthYearClass       Division        
 Length:1979433     Length:1979433     Length:1979433     Length:1979433     Min.   : 0.0     Length:1979433     Length:1979433     Length:1979433    
 Class :character   Class :character   Class :character   Class :character   1st Qu.:21.0     Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :28.0     Mode  :character   Mode  :character   Mode  :character  
                                                                             Mean   :31.4                                                             
                                                                             3rd Qu.:39.5                                                             
                                                                             Max.   :98.0                                                             
                                                                             NA's   :865345                                                           
  BodyweightKg    WeightClassKg         Squat1Kg          Squat2Kg          Squat3Kg          Squat4Kg        Best3SquatKg       Bench1Kg      
 Min.   : 15.10   Length:1979433     Min.   :-555.0    Min.   :-580.0    Min.   :-600.5    Min.   :-550.0    Min.   :-477.5   Min.   :-502.5   
 1st Qu.: 67.00   Class :character   1st Qu.:  87.5    1st Qu.:  70.0    1st Qu.:-162.5    1st Qu.:-110.0    1st Qu.: 122.5   1st Qu.:  56.7   
 Median : 81.92   Mode  :character   Median : 145.0    Median : 145.0    Median : 110.0    Median : 130.0    Median : 170.0   Median : 105.0   
 Mean   : 84.11                      Mean   : 113.0    Mean   :  92.4    Mean   :  31.7    Mean   :  68.9    Mean   : 175.1   Mean   :  83.7   
 3rd Qu.: 99.00                      3rd Qu.: 200.0    3rd Qu.: 205.0    3rd Qu.: 192.5    3rd Qu.: 201.0    3rd Qu.: 220.0   3rd Qu.: 145.0   
 Max.   :260.20                      Max.   : 555.0    Max.   : 577.5    Max.   : 560.0    Max.   : 505.5    Max.   : 580.0   Max.   : 467.5   
 NA's   :27067                       NA's   :1498188   NA's   :1503907   NA's   :1517871   NA's   :1974491   NA's   :605421   NA's   :1241513  
    Bench2Kg          Bench3Kg          Bench4Kg        Best3BenchKg     Deadlift1Kg       Deadlift2Kg       Deadlift3Kg       Deadlift4Kg     
 Min.   :-575.0    Min.   :-575.0    Min.   :-500.0    Min.   :-522.5   Min.   :-461.0    Min.   :-470.0    Min.   :-587.5    Min.   :-461.0   
 1st Qu.: -50.0    1st Qu.:-137.5    1st Qu.:-128.0    1st Qu.:  75.0   1st Qu.: 125.0    1st Qu.: 115.0    1st Qu.:-207.5    1st Qu.:-117.5   
 Median :  95.0    Median : -60.0    Median :  75.0    Median : 115.0   Median : 180.0    Median : 177.5    Median : 117.5    Median : 143.0   
 Mean   :  55.2    Mean   : -18.2    Mean   :  22.2    Mean   : 118.2   Mean   : 160.9    Mean   : 130.1    Mean   :  15.2    Mean   :  75.4   
 3rd Qu.: 145.0    3rd Qu.: 117.5    3rd Qu.: 156.2    3rd Qu.: 152.5   3rd Qu.: 225.0    3rd Qu.: 230.0    3rd Qu.: 205.0    3rd Qu.: 208.5   
 Max.   : 487.5    Max.   : 478.5    Max.   : 487.6    Max.   : 488.5   Max.   : 450.0    Max.   : 460.4    Max.   : 457.5    Max.   : 440.5   
 NA's   :1250750   NA's   :1273724   NA's   :1966266   NA's   :227920   NA's   :1444994   NA's   :1455794   NA's   :1479964   NA's   :1966724  
 Best3DeadliftKg     TotalKg          Place                Dots            Wilks         Glossbrenner       Goodlift         Tested         
 Min.   :-410.0   Min.   :   1.0   Length:1979433     Min.   :  0.68   Min.   :  0.67   Min.   :  0.64   Min.   :  0.50   Length:1979433    
 1st Qu.: 140.0   1st Qu.: 217.7   Class :character   1st Qu.:167.30   1st Qu.:166.56   1st Qu.:156.78   1st Qu.: 51.98   Class :character  
 Median : 188.2   Median : 369.7   Mode  :character   Median :303.25   Median :302.34   Median :282.75   Median : 63.31   Mode  :character  
 Mean   : 189.1   Mean   : 388.8                      Mean   :283.48   Mean   :282.47   Mean   :266.23   Mean   : 63.77                     
 3rd Qu.: 235.0   3rd Qu.: 540.0                      3rd Qu.:375.80   3rd Qu.:374.24   3rd Qu.:354.59   3rd Qu.: 75.01                     
 Max.   : 460.4   Max.   :1407.5                      Max.   :795.22   Max.   :793.33   Max.   :756.90   Max.   :146.49                     
 NA's   :515012   NA's   :146611                      NA's   :163883   NA's   :163883   NA's   :163883   NA's   :305726                     
   Country           Federation        ParentFederation       Date           MeetCountry         MeetState           MeetTown        
 Length:1979433     Length:1979433     Length:1979433     Length:1979433     Length:1979433     Length:1979433     Length:1979433    
 Class :character   Class :character   Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                                                                     
                                                                                                                                     
                                                                                                                                     
                                                                                                                                     
   MeetName        
 Length:1979433    
 Class :character  
 Mode  :character  
                   
                   
                   
                   

filter data

data.working <- data.working %>%
  filter(Event == "SBD",
         Equipment == "Raw",
         !is.na(Age),
         !is.na(BodyweightKg),
         !is.na(TotalKg),
         ParentFederation == "IPF")

creating features

data.working <- data.working %>%
  mutate(AgeBucket = as.factor(case_when(
         Age <= 19 ~ "Younger",
         Age >= 20 & Age <= 23 ~ "Junior",
         Age >= 24 & Age <= 35 ~ "Open",
         Age >= 36 ~ "Master",
         TRUE ~ "ERROR"))) %>%
  mutate(Federation = as.factor(Federation))
# Adding weightclass
data.working$WeightclassKg_Calc <- "error"
data.working[data.working$Sex == "M","WeightclassKg_Calc"] <- data.working %>% 
  filter(Sex == "M") %>%
  transmute(WeightclassKg_Calc = as.character(cut(BodyweightKg,
                                  c(0,53,59,66,74,83,93,105,120,9999))))
data.working[data.working$Sex == "F","WeightclassKg_Calc"] <- data.working %>% 
  filter(Sex == "F") %>%
  transmute(WeightclassKg_Calc = as.character(cut(BodyweightKg,
                                  c(0,43,47,52,57,63,72,84,9999))))
data.working$WeightclassKg_Calc <- as.factor(data.working$WeightclassKg_Calc)
weightclasses <- unique(data.working$WeightclassKg_Calc)

intra-weightclass scam

monotone increasing concave --> bs = "micv"

for (each in weightclasses){
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
  
  df_tmp <- data.working %>%
    filter(WeightclassKg_Calc == each) %>%
    select(TotalKg, BodyweightKg)
  
  scam_tmp <- scam(data = df_tmp,
                   TotalKg ~ s(BodyweightKg, bs = "mpi"))
  
  df_tmp$scam_predict <- predict(scam_tmp)
  
  print(ggplot(df_tmp) +
    geom_point(aes(x = BodyweightKg, y = TotalKg)) +
    geom_line(aes(x = BodyweightKg, y = scam_predict), color = "blue", size = 2) +
    ggtitle(paste(each, ": Raw with GAM")))
  
  data.working[data.working$WeightclassKg_Calc == each, "TotalKg_scam"] <- data.working[data.working$WeightclassKg_Calc == each, "TotalKg"] / df_tmp$scam_predict
  
  print(ggplot(data.working %>% filter(WeightclassKg_Calc == each)) +
    geom_point(aes(x = BodyweightKg, y = TotalKg_scam)) +
    ggtitle(paste(each, ": Transformed")))
}

ggplot(data.working %>% filter(Sex == "M")) +
  geom_point(aes(x = BodyweightKg, y = TotalKg_scam))

ggplot(data.working %>% filter(Sex == "F")) +
  geom_point(aes(x = BodyweightKg, y = TotalKg_scam))

box-cox

box_cox_df <- matrix(ncol = 2,
                     nrow = length(unique(data.working$WeightclassKg_Calc)))
# dataframe of optimal lambdas
for (x in 1:length(weightclasses)){
  tmp <- data.working %>% filter(WeightclassKg_Calc == weightclasses[x])
  box_cox_df[x,1] <- as.character(weightclasses[x])
  box_cox_df[x,2] <- powerTransform(tmp$TotalKg_scam)$lambda
}
box_cox_df <- as.data.frame(box_cox_df)
colnames(box_cox_df) <- c("WeightclassKg", "lambda")
box_cox_df$WeightclassKg <- as.character(box_cox_df$WeightclassKg)
box_cox_df$lambda <- as.numeric(box_cox_df$lambda)
box_cox_df
# applying box-cox w/ said optimal lambda
data.working$SCORE <- 0
for (each in weightclasses){
  x = data.working[data.working$WeightclassKg_Calc == each,"TotalKg_scam"]
  data.working[data.working$WeightclassKg_Calc == each,"SCORE"] <- bcPower(x,box_cox_df[box_cox_df$WeightclassKg == each,"lambda"])
}
# divide by mean
for (each in weightclasses){
  data.working[data.working$WeightclassKg_Calc == each,"SCORE"] <- scale(data.working[data.working$WeightclassKg_Calc == each,"SCORE"])
}

visualize box cox results

# individual weight classes
for (each in weightclasses){
  tmp <- data.working %>%
    filter(WeightclassKg_Calc == each) %>%
    select(SCORE)
  
  gg <- ggplot(tmp) +
    geom_density(aes(x = SCORE)) +
    ggtitle(each)
  
  print(gg)
}

# male / female
for (each in c("M", "F")){
  print(ggplot(data.working %>% filter(Sex == each)) +
    geom_point((aes(x = BodyweightKg, y = SCORE))) +
    ggtitle(paste("Gender =",each)))
}

Top Men & Women

data.working %>%
  arrange(desc(SCORE)) %>%
  filter(Sex == "M") %>%
  filter(row_number() <11)
data.working %>%
  arrange(desc(SCORE)) %>%
  filter(Sex == "F") %>%
  filter(row_number() <11)
NA
LS0tCnRpdGxlOiAib3Blbi1wb3dlcmxpZnRpbmctR0FNIgphdXRob3I6ICJKb2huIE15c2xpbnNraSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQojIHNldHVwCmBgYHtyIHNldHVwLCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nPUZBTFNFfQpybShsaXN0ID0gbHMoKSkKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyKQpsaWJyYXJ5KHNjYW0pCmBgYAoKIyBkb3dubG9hZCBkYXRhCmBgYHtyIGRvd25sb2FkLCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nPUZBTFNFfQppZiAoZmlsZS5leGlzdHMoIm9wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC9vcGVucG93ZXJsaWZ0aW5nLTIwMjAtMDYtMjAuY3N2IikpewogIGRhdGEud29ya2luZyA8LSByZWFkLmNzdigib3BlbnBvd2VybGlmdGluZy0yMDIwLTA2LTIwL29wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC5jc3YiKQp9IGVsc2UgewogIGRvd25sb2FkLmZpbGUoImh0dHBzOi8vZ2l0aHViLmNvbS9zc3RhbmdsL29wZW5wb3dlcmxpZnRpbmctc3RhdGljL3Jhdy9naC1wYWdlcy9vcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiLAogICAgICAgICAgICAgICJvcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiKQogIHVuemlwKCJvcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiKQogIAogIGRhdGEud29ya2luZyA8LSByZWFkLmNzdigib3BlbnBvd2VybGlmdGluZy0yMDIwLTA2LTIwL29wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC5jc3YiKQp9CmBgYAoKIyBzdW1tYXJpemUgZGF0YQpgYGB7ciBnbGltcHNlfQpzdW1tYXJ5KGRhdGEud29ya2luZykKYGBgCgojIGZpbHRlciBkYXRhCmBgYHtyIGZpbHRlcn0KZGF0YS53b3JraW5nIDwtIGRhdGEud29ya2luZyAlPiUKICBmaWx0ZXIoRXZlbnQgPT0gIlNCRCIsCiAgICAgICAgIEVxdWlwbWVudCA9PSAiUmF3IiwKICAgICAgICAgIWlzLm5hKEFnZSksCiAgICAgICAgICFpcy5uYShCb2R5d2VpZ2h0S2cpLAogICAgICAgICAhaXMubmEoVG90YWxLZyksCiAgICAgICAgIFBhcmVudEZlZGVyYXRpb24gPT0gIklQRiIpCmBgYAoKIyBjcmVhdGluZyBmZWF0dXJlcwpgYGB7ciBmZWF0dXJlc30KZGF0YS53b3JraW5nIDwtIGRhdGEud29ya2luZyAlPiUKICBtdXRhdGUoQWdlQnVja2V0ID0gYXMuZmFjdG9yKGNhc2Vfd2hlbigKICAgICAgICAgQWdlIDw9IDE5IH4gIllvdW5nZXIiLAogICAgICAgICBBZ2UgPj0gMjAgJiBBZ2UgPD0gMjMgfiAiSnVuaW9yIiwKICAgICAgICAgQWdlID49IDI0ICYgQWdlIDw9IDM1IH4gIk9wZW4iLAogICAgICAgICBBZ2UgPj0gMzYgfiAiTWFzdGVyIiwKICAgICAgICAgVFJVRSB+ICJFUlJPUiIpKSkgJT4lCiAgbXV0YXRlKEZlZGVyYXRpb24gPSBhcy5mYWN0b3IoRmVkZXJhdGlvbikpCgoKIyBBZGRpbmcgd2VpZ2h0Y2xhc3MKZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA8LSAiZXJyb3IiCgpkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFNleCA9PSAiTSIsIldlaWdodGNsYXNzS2dfQ2FsYyJdIDwtIGRhdGEud29ya2luZyAlPiUgCiAgZmlsdGVyKFNleCA9PSAiTSIpICU+JQogIHRyYW5zbXV0ZShXZWlnaHRjbGFzc0tnX0NhbGMgPSBhcy5jaGFyYWN0ZXIoY3V0KEJvZHl3ZWlnaHRLZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMCw1Myw1OSw2Niw3NCw4Myw5MywxMDUsMTIwLDk5OTkpKSkpCgpkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFNleCA9PSAiRiIsIldlaWdodGNsYXNzS2dfQ2FsYyJdIDwtIGRhdGEud29ya2luZyAlPiUgCiAgZmlsdGVyKFNleCA9PSAiRiIpICU+JQogIHRyYW5zbXV0ZShXZWlnaHRjbGFzc0tnX0NhbGMgPSBhcy5jaGFyYWN0ZXIoY3V0KEJvZHl3ZWlnaHRLZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMCw0Myw0Nyw1Miw1Nyw2Myw3Miw4NCw5OTk5KSkpKQoKZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA8LSBhcy5mYWN0b3IoZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYykKYGBgCgpgYGB7cn0Kd2VpZ2h0Y2xhc3NlcyA8LSB1bmlxdWUoZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYykKYGBgCgoKIyBpbnRyYS13ZWlnaHRjbGFzcyBzY2FtCiMjIG1vbm90b25lIGluY3JlYXNpbmcgY29uY2F2ZSAtLT4gYnMgPSAibWljdiIKYGBge3J9CmZvciAoZWFjaCBpbiB3ZWlnaHRjbGFzc2VzKXsKICAKICBkZl90bXAgPC0gZGF0YS53b3JraW5nICU+JQogICAgZmlsdGVyKFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoKSAlPiUKICAgIHNlbGVjdChUb3RhbEtnLCBCb2R5d2VpZ2h0S2cpCiAgCiAgc2NhbV90bXAgPC0gc2NhbShkYXRhID0gZGZfdG1wLAogICAgICAgICAgICAgICAgICAgVG90YWxLZyB+IHMoQm9keXdlaWdodEtnLCBicyA9ICJtcGkiKSkKICAKICBkZl90bXAkc2NhbV9wcmVkaWN0IDwtIHByZWRpY3Qoc2NhbV90bXApCiAgCiAgcHJpbnQoZ2dwbG90KGRmX3RtcCkgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IEJvZHl3ZWlnaHRLZywgeSA9IFRvdGFsS2cpKSArCiAgICBnZW9tX2xpbmUoYWVzKHggPSBCb2R5d2VpZ2h0S2csIHkgPSBzY2FtX3ByZWRpY3QpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDIpICsKICAgIGdndGl0bGUocGFzdGUoZWFjaCwgIjogUmF3IHdpdGggR0FNIikpKQogIAogIGRhdGEud29ya2luZ1tkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjID09IGVhY2gsICJUb3RhbEtnX3NjYW0iXSA8LSBkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoLCAiVG90YWxLZyJdIC8gZGZfdG1wJHNjYW1fcHJlZGljdAogIAogIHByaW50KGdncGxvdChkYXRhLndvcmtpbmcgJT4lIGZpbHRlcihXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCkpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBCb2R5d2VpZ2h0S2csIHkgPSBUb3RhbEtnX3NjYW0pKSArCiAgICBnZ3RpdGxlKHBhc3RlKGVhY2gsICI6IFRyYW5zZm9ybWVkIikpKQp9CgoKZ2dwbG90KGRhdGEud29ya2luZyAlPiUgZmlsdGVyKFNleCA9PSAiTSIpKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IEJvZHl3ZWlnaHRLZywgeSA9IFRvdGFsS2dfc2NhbSkpCgpnZ3Bsb3QoZGF0YS53b3JraW5nICU+JSBmaWx0ZXIoU2V4ID09ICJGIikpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gQm9keXdlaWdodEtnLCB5ID0gVG90YWxLZ19zY2FtKSkKYGBgCgojIGJveC1jb3gKYGBge3IgYm94LWNveC10cmFuc2Zvcm19CmJveF9jb3hfZGYgPC0gbWF0cml4KG5jb2wgPSAyLAogICAgICAgICAgICAgICAgICAgICBucm93ID0gbGVuZ3RoKHVuaXF1ZShkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjKSkpCgojIGRhdGFmcmFtZSBvZiBvcHRpbWFsIGxhbWJkYXMKZm9yICh4IGluIDE6bGVuZ3RoKHdlaWdodGNsYXNzZXMpKXsKICB0bXAgPC0gZGF0YS53b3JraW5nICU+JSBmaWx0ZXIoV2VpZ2h0Y2xhc3NLZ19DYWxjID09IHdlaWdodGNsYXNzZXNbeF0pCiAgYm94X2NveF9kZlt4LDFdIDwtIGFzLmNoYXJhY3Rlcih3ZWlnaHRjbGFzc2VzW3hdKQogIGJveF9jb3hfZGZbeCwyXSA8LSBwb3dlclRyYW5zZm9ybSh0bXAkVG90YWxLZ19zY2FtKSRsYW1iZGEKfQoKYm94X2NveF9kZiA8LSBhcy5kYXRhLmZyYW1lKGJveF9jb3hfZGYpCmNvbG5hbWVzKGJveF9jb3hfZGYpIDwtIGMoIldlaWdodGNsYXNzS2ciLCAibGFtYmRhIikKYm94X2NveF9kZiRXZWlnaHRjbGFzc0tnIDwtIGFzLmNoYXJhY3Rlcihib3hfY294X2RmJFdlaWdodGNsYXNzS2cpCmJveF9jb3hfZGYkbGFtYmRhIDwtIGFzLm51bWVyaWMoYm94X2NveF9kZiRsYW1iZGEpCmJveF9jb3hfZGYKCiMgYXBwbHlpbmcgYm94LWNveCB3LyBzYWlkIG9wdGltYWwgbGFtYmRhCmRhdGEud29ya2luZyRTQ09SRSA8LSAwCmZvciAoZWFjaCBpbiB3ZWlnaHRjbGFzc2VzKXsKICB4ID0gZGF0YS53b3JraW5nW2RhdGEud29ya2luZyRXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCwiVG90YWxLZ19zY2FtIl0KICBkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoLCJTQ09SRSJdIDwtIGJjUG93ZXIoeCxib3hfY294X2RmW2JveF9jb3hfZGYkV2VpZ2h0Y2xhc3NLZyA9PSBlYWNoLCJsYW1iZGEiXSkKfQoKCiMgZGl2aWRlIGJ5IG1lYW4KZm9yIChlYWNoIGluIHdlaWdodGNsYXNzZXMpewogIGRhdGEud29ya2luZ1tkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjID09IGVhY2gsIlNDT1JFIl0gPC0gc2NhbGUoZGF0YS53b3JraW5nW2RhdGEud29ya2luZyRXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCwiU0NPUkUiXSkKfQpgYGAKCgojIHZpc3VhbGl6ZSBib3ggY294IHJlc3VsdHMKYGBge3Igdml6LWJveC1jb3h9CiMgaW5kaXZpZHVhbCB3ZWlnaHQgY2xhc3Nlcwpmb3IgKGVhY2ggaW4gd2VpZ2h0Y2xhc3Nlcyl7CiAgdG1wIDwtIGRhdGEud29ya2luZyAlPiUKICAgIGZpbHRlcihXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCkgJT4lCiAgICBzZWxlY3QoU0NPUkUpCiAgCiAgZ2cgPC0gZ2dwbG90KHRtcCkgKwogICAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gU0NPUkUpKSArCiAgICBnZ3RpdGxlKGVhY2gpCiAgCiAgcHJpbnQoZ2cpCn0KCiMgbWFsZSAvIGZlbWFsZQpmb3IgKGVhY2ggaW4gYygiTSIsICJGIikpewogIHByaW50KGdncGxvdChkYXRhLndvcmtpbmcgJT4lIGZpbHRlcihTZXggPT0gZWFjaCkpICsKICAgIGdlb21fcG9pbnQoKGFlcyh4ID0gQm9keXdlaWdodEtnLCB5ID0gU0NPUkUpKSkgKwogICAgZ2d0aXRsZShwYXN0ZSgiR2VuZGVyID0iLGVhY2gpKSkKfQoKYGBgCiMgVG9wIE1lbiAmIFdvbWVuCmBgYHtyfQpkYXRhLndvcmtpbmcgJT4lCiAgYXJyYW5nZShkZXNjKFNDT1JFKSkgJT4lCiAgZmlsdGVyKFNleCA9PSAiTSIpICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPDExKQoKCmRhdGEud29ya2luZyAlPiUKICBhcnJhbmdlKGRlc2MoU0NPUkUpKSAlPiUKICBmaWx0ZXIoU2V4ID09ICJGIikgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSA8MTEpCmBgYAoKCgoK